package com.janlei.tdd.practise;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

public class BowlingGame {

    int score(String input) {
        List<Frame> frames = frames(input);
        return frames.stream()
                .mapToInt(Frame::score)
                .sum();
    }

    List<Frame> frames(String input) {
        String[] frameAll = input.split("\\|\\|");
        String[] frames = frameAll[0].split("\\|");
        AtomicInteger index = new AtomicInteger();

        BiFunction<AtomicInteger, String, Frame> consumer =
                (idx, content) -> new Frame(index.getAndIncrement(), content);

        List<Frame> frameList = Arrays.stream(frames)
                .map(content -> consumer.apply(index, content))
                .collect(toList());

        // extra roll
        if (frameAll.length != 1) {
            frameList.add(new Frame(index.getAndIncrement(), frameAll[1]));
        }

        setExtras(frameList);
        return frameList;
    }

    private void setExtras(List<Frame> frames) {

        int size = frames.size();
        IntStream.range(0, size).forEach(i -> {
            Frame frame = frames.get(i);
            int type = frame.type();
            final StringBuilder extra = new StringBuilder();

            Frames util = new Frames(frames, i, type, extra);
            util.extra();

            frame.setExtra(util.extra.toString());
        });

    }
}

@AllArgsConstructor
class Frames {
    List<Frame> frames;
    int i;
    int type;
    StringBuilder extra;

    void extra() {
        if (type == 0) {
            return;
        }

        if (type == 1) {
            canSetExtraFor1();
            return;
        }

        if (type == 2) {
            canSetExtraFor2();
        }

    }

    private void canSetExtraFor1() {
        int size = frames.size();
        boolean exist = type == 1 && i + 1 < size;
        if (exist) {
            Frame nextFrame = frames.get(i + 1);
            String nextExtra = nextFrame.getInput();
            this.extra.append(nextExtra.charAt(0));
        }
    }

    private void canSetExtraFor2() {
        int size = frames.size();
        boolean flag = i + 1 < size;
        if (flag) {
            Frame nextFrame = frames.get(i + 1);
            String nextExtra = nextFrame.getInput();
            extra.append(nextExtra);
            boolean isExtraAfterNext = nextFrame.type() == 2
                    && i + 2 < size;
            if (isExtraAfterNext) {
                char nextExtraAfterNext = frames.get(i + 2)
                        .getInput()
                        .charAt(0);
                extra.append(nextExtraAfterNext);
            }
        }
    }
}

@AllArgsConstructor
@NoArgsConstructor
@Getter
class Frame {
    private static final Pattern PATTERN = Pattern.compile("^-?[1-9]\\d*$");
    private int index;
    private String input;
    private String extra;

    public Frame(int index, String input) {
        this.index = index;
        this.input = input;
        // default
        this.extra = "";
    }

    public void setExtra(String extra) {
        this.extra = extra;
    }

    int score() {
        return extra() + base();
    }

    private int base() {
        if (index > 9) {
            return 0;
        }
        return score(input);
    }

    private int extra() {
        return score(extra);
    }

    public int type() {
        if (strike(input) || strikeLast(input)) {
            return 2;
        }
        if (spare(input)) {
            return 1;
        }
        return 0;
    }

    private int score(String content) {
        requireNonNull(content);
        if (strikeLast(content)) {
            return 20;
        }
        if (strike(content) || spare(content)) {
            return 10;
        }

        if (miss(content)) {
            if (miss2(content)) {
                return 0;
            }
            String replace = content.replace("-", "");
            if (same(content, "")) {
                return 0;
            }
            return Integer.parseInt(replace);
        }

        boolean isNumber = PATTERN.matcher(content).matches();
        if (isNumber) {
            return Integer.parseInt(content);
        }
        throw new IllegalArgumentException("统计结果有误，请重新确认！");
    }

    public boolean strike(String content) {
        return same(content, "X");
    }

    private boolean strikeLast(String content) {
        return same(content, "XX");
    }

    public boolean spare(String content) {
        return content.contains("/");
    }

    public boolean miss(String content) {
        return same(content, "") || content.contains("-");
    }

    private boolean miss2(String content) {
        return content.contains("--");
    }

    private boolean same(String content, String x) {
        return x.equals(content);
    }
}